package ro.flashbite.helpers
{
	import ro.flashbite.errors.ArgumentIncompatibleError;
	import ro.flashbite.errors.DontInstanciateError;
	
	import flash.display.*;
	import flash.geom.*;
	import flash.system.ApplicationDomain;

	/**
	 * Static helpers for working with MovieClip objects
	 * 
	 * v 1.0
	 * 
	 * @author Adrian Barbu  
	 */	
	public final class MovieClipHelpers
	{
		public function MovieClipHelpers() { throw new DontInstanciateError(); }
		
		/**
		 * Parses the container for movie clips and sets them all to specified frame -- gotoAndStop(frame)
		 * 
		 * @param doc : (DisplayObjectContainer) movieclip to be used
		 * @param frame : (Object) : the frame on doc and alll his children will stop
		 */		
		static public function gotoAndStopRecursive(doc:DisplayObjectContainer, frame:Object = 1):void
		{
			if (doc == null) {
				throw new ArgumentIncompatibleError("doc");
			}
			
			var i	: int = 0,
				len : int = doc.numChildren,
				ch  : DisplayObjectContainer;
			
			while (i < len) {
				ch = doc.getChildAt(i) as DisplayObjectContainer;				
				i++;
				if (ch == null) continue;
				gotoAndStopRecursive(ch, frame);
			}
			if (doc is MovieClip) {
				(doc as MovieClip).gotoAndStop(frame);
			}
		}
		
		/**
		 * Parses the container for movie clips and sets them all to their last frame
		 * 
		 * @param doc : (DisplayObjectContainer) movieclip to be used
		 */		
		static public function gotoEndRecursive(doc:DisplayObjectContainer):void
		{
			if (doc == null) {
				throw new ArgumentIncompatibleError("doc");
			}
			
			var i	: int = 0,
				len : int = doc.numChildren,
				ch  : DisplayObjectContainer;
			
			while (i < len) {
				ch = doc.getChildAt(i) as DisplayObjectContainer;				
				i++;
				if (ch == null) continue;
				gotoEndRecursive(ch);
			}
			if (doc is MovieClip) {
				(doc as MovieClip).gotoAndStop(MovieClip(doc).totalFrames);
			}
		}
		
		/**
		 * Parses the container for movie clips and stop them
		 * 
		 * @param doc : (DisplayObjectContainer) movieclip to be used
		 * @param stopDOC : (Boolean) tells if we stop also the main movieclip
		 */	
		static public function stopRecursive(doc:DisplayObjectContainer, stopDOC:Boolean = true):void
		{
			if (doc == null) {
				throw new ArgumentIncompatibleError("doc");
			}
			
			if (doc is MovieClip && stopDOC) {
				(doc as MovieClip).stop();
			}
			
			var i	: int = 0,
				len : int = doc.numChildren,
				ch  : DisplayObjectContainer;
			
			while (i < len) {
				ch = doc.getChildAt(i) as DisplayObjectContainer;				
				i++;
				if (ch == null) continue;
				stopRecursive(ch);
			}
		}
		
		/**
		 * Parses the container for movie clips and play them
		 * 
		 * @param doc : (DisplayObjectContainer) movieclip to be used
		 * @param stopDOC : (Boolean) tells if we play also the main movieclip
		 */		
		static public function playRecursive(doc:DisplayObjectContainer, playDOC:Boolean = true):void
		{
			if (doc == null) {
				throw new ArgumentIncompatibleError("doc");
			}
			
			var i	: int = 0,
				len : int = doc.numChildren,
				ch  : DisplayObjectContainer;
			
			while (i < len) {
				ch = doc.getChildAt(i) as DisplayObjectContainer;				
				i++;
				if (ch == null) continue;
				playRecursive(ch);
			}
			if (doc is MovieClip && playDOC) {
				(doc as MovieClip).play();
			}
		}
		
		/**
		 * Removes a display object from its parent
		 *  
		 * @param dObj : (DisplayObject) item to be removed from parent
		 * @return : (Boolean) true id removal was succesfully, false otherwise
		 */		
		static public function removeFromParent(dObj:DisplayObject):Boolean
		{
			if (dObj == null) {
				throw new ArgumentIncompatibleError("dObj");
			}
			
			var success:Boolean;
			
			if(dObj.parent){
				dObj.parent.removeChild(dObj);                    
				success = true;
			}
			
			return success;
		}
		
		/**
		 * Gets the frame number of a specified label
		 *  
		 * @param mcInstance : (MovieClip) the movieClip to search in
		 * @param label : (String) the frame label name to search
		 * @return : (int) -1 if the label isn't present in movieClip, frame number otherwise
		 */		
		static public function label2Frame(mcInstance:MovieClip, label:String):int
		{
			if (mcInstance == null) {
				throw new ArgumentIncompatibleError("mcInstance");
			}
			if (label == null) {
				throw new ArgumentIncompatibleError("label");
			}	
			
			var labels	: Array = mcInstance.currentLabels,
				i		: int,
				len		: uint 	= labels.length;
			
			for (i = 0; i < len; i++) {
				if ((labels[i] as FrameLabel).name == label) {
					return (labels[i] as FrameLabel).frame;
				}
			}
			
			return -1;
		}
		
		/**
		 * Gets all MovieClip type children of a specified container
		 *  
		 * @param doc : (DisplayObjectContainer) the container to search in
		 * @return : (Array) array with the movie clip's children instances
		 */		
		public static function getMcChildren(doc:DisplayObjectContainer):Array
		{
			if (doc == null) {
				throw new ArgumentIncompatibleError("doc");
			}
			
			var children	: Array = [],
				child		: MovieClip,
				i			: Number = doc.numChildren - 1;
			
			do {
				child = doc.getChildAt(i) as MovieClip;				
				i--;				
				if (child) children.push(child);
				
			} while (i >= 0)
			
			return children;
		}
		
		/**
		 * Removes all children of a container
		 * 
		 * @param doc : (DisplayObjectContainer) container to 'clear'
		 */		
		static public function removeAllChildren(doc:DisplayObjectContainer):void
		{
			if (doc == null) {
				throw new ArgumentIncompatibleError("doc");
			}
			
			while (doc.numChildren > 0) 
				doc.removeChildAt(0); 
		}
		
		/**
		 * Disables mouseEvents on all children of a movieClip
		 *  
		 * @param io : (InteractiveObject) container to remove interaction on
		 */		
		static public function removeMouseInteraction(io:InteractiveObject) :void 
		{
			if (io == null) {
				throw new ArgumentIncompatibleError("io");
			}
			
			var doc:DisplayObjectContainer = io as DisplayObjectContainer;
			if (doc) {
				doc.mouseChildren = false;
				io.mouseEnabled = false;
			}
			
			var i	: int = 0,
				len : int = (doc) ? doc.numChildren : 0,
				ch  : DisplayObjectContainer;
			
			while (i < len) {
				ch = doc.getChildAt(i) as DisplayObjectContainer;				
				i++;
				if (ch == null) continue;
				removeMouseInteraction(ch);
			}
		}
		
		/**
		 * Disables mouseEvents on all children of a movieClip
		 *  
		 * @param io : (InteractiveObject) container to remove interaction on
		 * @param stopAtFirstMouseEnabledParent : (Boolean) flag to stop at first parent with mouseEnabled == true
		 */	
		static public function enableMouseInteraction(io:InteractiveObject, stopAtFirstMouseEnabledParent:Boolean = true) :void 
		{
			if (io == null) {
				throw new ArgumentIncompatibleError("io");
			}
			
			io.mouseEnabled = true;
			var p : DisplayObjectContainer = io.parent;
			
			while (p) {
				if (stopAtFirstMouseEnabledParent && p.mouseChildren) break;
				p.mouseChildren = true;
				p = p.parent;
			}
		}
		
		/**
		 * Extracts a DisplayObject using a path like: "parent.child1.child3.child12" 
		 * 
		 * @param mcParent : (MovieClip) movieclip to search in
		 * @param path : (String) path to child
		 * @param pathSeparator : (String) path separator used in path
		 * @return : (DisplayObject) the object created, otherwise throw a error message
		 */		
		static public function extract(mcParent:MovieClip, path:String, pathSeparator:String = "."):DisplayObject 
		{
			if (mcParent == null) {
				throw new ArgumentIncompatibleError("mcParent");
			}
			if (path == null) {
				throw new ArgumentIncompatibleError("path");
			}
			
			var parts	: Array = path.split(pathSeparator),
				ret		: DisplayObject = mcParent,
				part	: String;
			
			for each (part in parts) {
				if (ret.hasOwnProperty(part)) {
					ret = ret[part]; 
				} else throw new Error(String("'" + part + "' specified in '" + path + "' was not found within movieclip!"));
			}
			
			return ret;
		}
		
		/**
		 * Create a Movieclip from application Domain
		 *  
		 * @param appDom : (ApplicationDomain) domanin to use
		 * @param librName : (String) the name given as linkage in flash
		 * @return : (MovieClip) a new instance of MovieClip with given name from application Domain
		 */		
		public static function createMcFromAppDom(appDom:ApplicationDomain, librName:String):MovieClip
		{
			if (appDom == null) {
				throw new ArgumentIncompatibleError("appDom");
			}
			if (StringHelpers.isEmpty(librName)) {
				throw new ArgumentIncompatibleError("librName");
			}
			
			var graphicsClass	: Class  = appDom.getDefinition(librName) as Class,
				newGraphics		: Object = new graphicsClass() as Object;
			
			return newGraphics as MovieClip;
		}
		
		/**
		 * Create class alias from appDom
		 * 
		 * @param appDom : (ApplicationDomain) domanin to use
		 * @param librName : (String) the name given as linkage in flash
		 * @return : (Class) a class 'name' from application domain
		 */		
		public static function getClassFromAppDom(appDom:ApplicationDomain, librName:String):Class
		{
			if (appDom == null) {
				throw new ArgumentIncompatibleError("appDom");
			}
			if (StringHelpers.isEmpty(librName)) {
				throw new ArgumentIncompatibleError("librName");
			}
			
			return appDom.getDefinition(librName) as Class;
		}
		
		/**
		 * Change a DisplayObject color using item.transform.colorTransform
		 *  
		 * @param mc : (DisplayObject) the object used
		 * @param newColor : (uint) the new color to apply to item
		 */		
		public static function changeColor(mc:DisplayObject, newColor:uint):void
		{
			if (mc == null) {
				throw new ArgumentIncompatibleError("mc");
			}
			
			var colorInfo : ColorTransform = mc.transform.colorTransform;
			
			colorInfo.color = newColor;
			mc.transform.colorTransform = colorInfo;
		}
		
		/**
		 * Align a displayObject to the horizontal center of the bounding Rectangle
		 * 
		 * @param displayObject : (DisplayObject) to be 'moved'
		 * @param bounds : (Rectangle) rect used to mark as bounds
		 * @param snapToPixel: (Boolean) Force the position to whole pixels (true), or to let the DisplayObject be positioned on sub-pixels (false)
		 */		
		public static function alignCenter(displayObject:DisplayObject, bounds:Rectangle, snapToPixel:Boolean = true):void 
		{
			if (displayObject == null) {
				throw new ArgumentIncompatibleError("displayObject");
			}
			if (bounds == null) {
				throw new ArgumentIncompatibleError("bounds");
			}
			
			var centerX : Number = bounds.width * 0.5 - displayObject.width * 0.5 + bounds.x;
			
			displayObject.x = snapToPixel ? Math.round(centerX) : centerX;
		}
		
		/**
		 * Align a displayObject to the vertical middle of the bounding Rectangle
		 * 
		 * @param displayObject : (DisplayObject) to be 'moved'
		 * @param bounds : (Rectangle) rect used to mark as bounds
		 * @param snapToPixel: (Boolean) Force the position to whole pixels (true), or to let the DisplayObject be positioned on sub-pixels (false)
		 */		
		public static function alignMiddle(displayObject:DisplayObject, bounds:Rectangle, snapToPixel:Boolean = true):void 
		{
			if (displayObject == null) {
				throw new ArgumentIncompatibleError("displayObject");
			}
			if (bounds == null) {
				throw new ArgumentIncompatibleError("bounds");
			}
			
			var centerY : Number = bounds.height * 0.5 - displayObject.height * 0.5 + bounds.y;
			
			displayObject.y = snapToPixel ? Math.round(centerY) : centerY;
		}
		
		/**
		 * Align a displayObject to the horizontal center and vertical middle of the bounding Rectangle 
		 * 
		 * @param displayObject : (DisplayObject) to be 'moved'
		 * @param bounds : (Rectangle) rect used to mark as bounds
		 * @param snapToPixel: (Boolean) Force the position to whole pixels (true), or to let the DisplayObject be positioned on sub-pixels (false)
		 */			
		public static function alignCenterMiddle(displayObject:DisplayObject, bounds:Rectangle, snapToPixel:Boolean = true):void 
		{
			alignCenter(displayObject, bounds, snapToPixel);
			alignMiddle(displayObject, bounds, snapToPixel);
		}
		
		/**
		 * Create a bitmap from displayObject
		 *  
		 * @param displObj : (DisplayObject) object to be 'cloned' as bitmap
		 * @param smoothing : (Boolean) smoothing parameter from Bitmap
		 * @return : (Bitmap) the bitmpa representation of DisplayObject
		 */		
		public static function createBitmap(displObj:DisplayObject, smoothing:Boolean = true):Bitmap
		{
			if (displObj == null) {
				throw new ArgumentIncompatibleError("displObj");
			}
			
			var oldXY:Point = new Point(displObj.x, displObj.y);
			
			displObj.x = 0; displObj.y = 0;
			
			var rect	: Rectangle = displObj.getBounds(displObj),
				bmpData : BitmapData = new BitmapData(displObj.width, displObj.height, true, 0x00ffffff),
				m		: Matrix = displObj.transform.matrix.clone();
			
			m.translate(-rect.x, -rect.y);
			rect = new Rectangle(0, 0, displObj.width, displObj.height);
			bmpData.draw(displObj, m, null, null, null, smoothing);
			
			displObj.x = oldXY.x; displObj.y = oldXY.y;
			
			return new Bitmap(bmpData, PixelSnapping.NEVER, smoothing);
		}
		
		/**
		 * Returns the registration point of the DisplayObject in local coordinates 
		 *  
		 * @param displayObject : (DisplayObject) object to be used
		 * @return : (Point) the local coordonates of the registration point of item
		 */		
		public static function getRegistrationPoint(displayObject:DisplayObject):Point
		{
			if (displayObject == null) {
				throw new ArgumentIncompatibleError("displayObject");
			}
			
			var m		: Matrix = displayObject.transform.matrix.clone(),			  
				bounds	: Rectangle ;
			
			displayObject.transform.matrix = new Matrix();
			bounds = displayObject.getBounds(null);
			displayObject.transform.matrix = m;
			
			return new Point(bounds.x, bounds.y);
		}
		
		/**
		 * Scale a display object so that he must fit in given dimensions 
		 * 
		 * @param dObj  : (DisplayObject) to be scalled
		 * @param width : (Number) width to fit in
		 * @param height : (Number) height to fit in
		 */		
		public static function scaleToWH(dObj:DisplayObject, width:Number, height:Number):void
		{
			if (dObj == null) {
				throw new ArgumentIncompatibleError("dObj");
			}
			if (width <= 0) {
				throw new ArgumentIncompatibleError("width");
			}
			if (height <= 0) {
				throw new ArgumentIncompatibleError("height");
			}
			
			//on some assets with different scale applied to them, even if they are scalled, they don't match the width and height provided
			var percentage:Number = 1;
			if (dObj.width > width || dObj.height > height) {
				//smaller
				percentage = Math.min(width/dObj.width, height/dObj.height);
			} else if (dObj.width < width && dObj.height < height) {
				//bigger
				percentage = Math.min(width/dObj.width, height/dObj.height);
			}
			//real scalling
			dObj.width  *= percentage;
			dObj.height *= percentage;
		}
	}
}